<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <h1 data-lake-id="Jw2ka" id="Jw2ka"><span data-lake-id="ua62ed755" id="ua62ed755">典型回答</span></h1>
  <p data-lake-id="u821dc85a" id="u821dc85a"><br></p>
  <p data-lake-id="u96186bb0" id="u96186bb0"><br></p>
  <p data-lake-id="u1ab9866d" id="u1ab9866d"><span data-lake-id="ufc7ca455" id="ufc7ca455">ThreadLocal的内存泄露问题是一个比较典型的问题，可以说这个问题，</span><strong><span data-lake-id="uc9a047ca" id="uc9a047ca">ThreadLocal帮我们解决了一半，还有一半需要开发者自己解决。</span></strong></p>
  <p data-lake-id="u0af4c75f" id="u0af4c75f"><strong><span data-lake-id="u23869c44" id="u23869c44">​</span></strong><br></p>
  <p data-lake-id="uce5ca49b" id="uce5ca49b"><br></p>
  <h3 data-lake-id="RJXZP" id="RJXZP"><span data-lake-id="u959ce393" id="u959ce393">内存泄漏来自哪？</span></h3>
  <p data-lake-id="uc528106c" id="uc528106c"><br></p>
  <p data-lake-id="u495c03e2" id="u495c03e2"><span data-lake-id="ude4c94fa" id="ude4c94fa">会导致ThreadLocal内存泄漏的部分其实就是他在堆上存储的ThreadLocalMap中的K-V部分：</span></p>
  <p data-lake-id="u217f8b14" id="u217f8b14"><img src="https://cdn.nlark.com/yuque/0/2023/png/5378072/1688455648638-c0d80f68-d297-4ac4-9f64-8e733f14627b.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_43%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"></p>
  <p data-lake-id="ua9f66dd5" id="ua9f66dd5"><br></p>
  <p data-lake-id="u19e56841" id="u19e56841"><span data-lake-id="u6bea8b9a" id="u6bea8b9a">ThreadLocalMap的key就是ThreadLocal对象，他有两个引用源，一</span><strong><span data-lake-id="u5a477805" id="u5a477805">个是栈上的ThreadLocal引用，一个是ThreadLocalMap中的Key对他的引用。</span></strong></p>
  <p data-lake-id="u3640ff21" id="u3640ff21"><img src="https://cdn.nlark.com/yuque/0/2023/png/5378072/1703300494076-f8280388-b9d2-4463-a6d4-b798bd835138.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_50%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"></p>
  <p data-lake-id="u0fa14a9a" id="u0fa14a9a"><br></p>
  <p data-lake-id="uda181b89" id="uda181b89"><span data-lake-id="u928cb243" id="u928cb243">而对于value来说，他的引用就一条，就是从Thread对象过来的。</span></p>
  <p data-lake-id="u903eaac2" id="u903eaac2"><span data-lake-id="ud1958212" id="ud1958212">​</span><br></p>
  <p data-lake-id="u7649460e" id="u7649460e"><img src="https://cdn.nlark.com/yuque/0/2023/png/5378072/1703300549782-8c52ff01-880f-44c2-9564-7bb1919bfcc4.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_56%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"></p>
  <p data-lake-id="u38e30ca8" id="u38e30ca8"><span data-lake-id="uf3f4b8ad" id="uf3f4b8ad">​</span><br></p>
  <p data-lake-id="u25415d1e" id="u25415d1e"><span data-lake-id="u7b043b1f" id="u7b043b1f">所以，就会出现以下两种情况：</span></p>
  <p data-lake-id="u74018e37" id="u74018e37"><span data-lake-id="ub29d710c" id="ub29d710c">​</span><br></p>
  <p data-lake-id="ua18c663b" id="ua18c663b"><span data-lake-id="ucd58d66f" id="ucd58d66f">1、栈上的ThreadLocal Ref引用不在使用了，即方法结束后这个对象引用就不再用了，那么，ThreadLocal对象因为还有一条引用链在，所以就会导致他无法被回收，久而久之可能就会对导致OOM。</span></p>
  <p data-lake-id="u1bb5ed0d" id="u1bb5ed0d"><span data-lake-id="u4879cbe0" id="u4879cbe0">​</span><br></p>
  <p data-lake-id="u340ceec7" id="u340ceec7"><img src="https://cdn.nlark.com/yuque/0/2023/png/5378072/1703300619609-57783a8a-7da4-4813-b310-a34d9b4d3641.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_51%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"></p>
  <p data-lake-id="u7d62fdc3" id="u7d62fdc3"><span data-lake-id="u33749f7c" id="u33749f7c">​</span><br></p>
  <p data-lake-id="uf4d654e1" id="uf4d654e1"><span data-lake-id="u4ad76a18" id="u4ad76a18">2、Thread对象如果一直在被使用，比如在线程池中被重复使用，那么从这条引用链就一直在，那么就会导致ThreadLocalMap无法被回收。</span></p>
  <p data-lake-id="uc4f07c2b" id="uc4f07c2b"><span data-lake-id="ue6a3868a" id="ue6a3868a">​</span><br></p>
  <p data-lake-id="u0cc03cb9" id="u0cc03cb9"><img src="https://cdn.nlark.com/yuque/0/2023/png/5378072/1703300655575-0cb5f021-ca9d-49b9-893d-7f4be614edd4.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_63%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"></p>
  <p data-lake-id="u16508c23" id="u16508c23"><br></p>
  <h3 data-lake-id="va4DK" id="va4DK"><span data-lake-id="uf96d66b4" id="uf96d66b4">弱引用解决内存泄漏</span></h3>
  <p data-lake-id="uc6ff2291" id="uc6ff2291"><br></p>
  <p data-lake-id="u9c2139bc" id="u9c2139bc"><span data-lake-id="u78f3f068" id="u78f3f068">我们先来说说JDK自己帮我们实现的一部分功能，主要是解决上面说的第一种情况：</span></p>
  <p data-lake-id="uc3c0bc26" id="uc3c0bc26"><span data-lake-id="u3af02080" id="u3af02080">​</span><br></p>
  <blockquote data-lake-id="u712d8b7b" id="u712d8b7b">
   <p data-lake-id="u8b48697a" id="u8b48697a"><span data-lake-id="u4c2e1aca" id="u4c2e1aca">栈上的ThreadLocal Ref引用不在使用了，即方法结束后这个对象引用就不再用了，那么，ThreadLocal对象因为还有一条引用链在，所以就会导致他无法被回收，久而久之可能就会对导致OOM。</span></p>
  </blockquote>
  <p data-lake-id="ue42759b6" id="ue42759b6"><br></p>
  <p data-lake-id="u196edf48" id="u196edf48" style="text-align: justify"><span data-lake-id="uee52836b" id="uee52836b">为了解决这个问题，ThreadLocalMap使用了</span><strong><span data-lake-id="u8abd4876" id="u8abd4876">弱引用</span></strong><span data-lake-id="u97be2eac" id="u97be2eac">。</span></p>
  <p data-lake-id="u2094886d" id="u2094886d" style="text-align: justify"><span data-lake-id="ua0f3cf00" id="ua0f3cf00">​</span><br></p>
  <p data-lake-id="u5536b6a7" id="u5536b6a7" style="text-align: justify"><img src="https://cdn.nlark.com/yuque/0/2023/png/5378072/1703300791266-fd9e23ea-0cb7-434c-a389-131b7ed9cef0.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_51%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"></p>
  <p data-lake-id="u5ef362b0" id="u5ef362b0" style="text-align: justify"><span data-lake-id="ufb040acf" id="ufb040acf">​</span><br></p>
  <p data-lake-id="u71f139eb" id="u71f139eb" style="text-align: justify"><span data-lake-id="u44ab75a3" id="u44ab75a3">从ThreadLocalMap中的一段代码说起的，这段代码就是Entry的构造方法：</span></p>
  <p data-lake-id="u3240b3ed" id="u3240b3ed"><span data-lake-id="uf05cf7ee" id="uf05cf7ee" style="color: rgb(102, 217, 239)"> </span></p>
  <pre lang="java"><code>
static class Entry extends WeakReference&lt;ThreadLocal&lt;?&gt;&gt; {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal&lt;?&gt; k, Object v) {
        super(k);
        value = v;
    }
}
</code></pre>
  <p data-lake-id="uf300199c" id="uf300199c"><span data-lake-id="u6b9a2c22" id="u6b9a2c22" style="color: rgb(102, 217, 239)"> </span><span data-lake-id="u7414c7c7" id="u7414c7c7" style="color: rgb(248, 248, 242)">}</span><span data-lake-id="u0ab6946c" id="u0ab6946c" style="color: rgb(102, 217, 239)">​</span></p>
  <p data-lake-id="u15a945b7" id="u15a945b7"><span data-lake-id="u5cac06dd" id="u5cac06dd">我们从ThreadLocal的内部静态类Entry的代码设计可知，ThreadLocal的引用k通过构造方法传递给了Entry类的父类WeakReference的构造方法，从这个层面来说，可以理解ThreadLocalMap中的键是ThreadLocal的弱引用。</span></p>
  <p data-lake-id="uf5083014" id="uf5083014"><br></p>
  <blockquote data-lake-id="uf857eea4" id="uf857eea4">
   <p data-lake-id="uddc382ce" id="uddc382ce"><span data-lake-id="u6fad1f13" id="u6fad1f13">强引用：Java中默认的引用类型，一个对象如果具有强引用那么只要这种引用还存在就不会被回收。比如String str = new String("Hello ThreadLocal");，其中str就是一个强引用，当然，一旦强引用出了其作用域，那么强引用随着方法弹出线程栈，那么它所指向的对象将在合适的时机被JVM垃圾收集器回收。</span></p>
   <p data-lake-id="uc5222344" id="uc5222344"><span data-lake-id="u74fc81af" id="u74fc81af">​</span><br></p>
   <p data-lake-id="ua4305a1e" id="ua4305a1e"><span data-lake-id="ud184319f" id="ud184319f">软引用：如果一个对象具有软引用，在JVM发生内存溢出之前（即内存充足够使用），是不会GC这个对象的；只有到JVM内存不足的时候才会调用垃圾回收期回收掉这个对象。软引用和一个引用队列联合使用，如果软引用所引用的对象被回收之后，该引用就会加入到与之关联的引用队列中。</span></p>
   <p data-lake-id="u6420b3be" id="u6420b3be"><span data-lake-id="u6d29c548" id="u6d29c548">​</span><br></p>
   <p data-lake-id="u092b137e" id="u092b137e"><span data-lake-id="uaf2e3748" id="uaf2e3748">弱引用：这里讨论ThreadLocalMap中的Entry类的重点，如果一个对象只具有弱引用，那么这个对象就会被垃圾回收器回收掉（被弱引用所引用的对象只能生存到下一次GC之前，当发生GC时候，无论当前内存是否足够，弱引用所引用的对象都会被回收掉）。弱引用也是和一个引用队列联合使用，如果弱引用的对象被垃圾回收期回收掉，JVM会将这个引用加入到与之关联的引用队列中。弱引用的对象可以通过弱引用的get方法得到，当引用的对象被回收掉之后，再调用get方法就会返回null。</span></p>
   <p data-lake-id="u849afc5c" id="u849afc5c"><span data-lake-id="uff4d787e" id="uff4d787e">​</span><br></p>
   <p data-lake-id="u9eac6d06" id="u9eac6d06"><span data-lake-id="ubec93082" id="ubec93082">虚引用：虚引用是所有引用中最弱的一种引用，其存在就是为了将关联虚引用的对象在被GC掉之后收到一个通知。</span></p>
  </blockquote>
  <p data-lake-id="ud2a1c4de" id="ud2a1c4de"><span data-lake-id="ue4a3fcfe" id="ue4a3fcfe">​</span><br></p>
  <p data-lake-id="u40953d85" id="u40953d85"><span data-lake-id="ubae333bd" id="ubae333bd">如果用了弱引用，那么ThreadLocal对象就可以在下次GC的时候被回收掉了。</span></p>
  <p data-lake-id="u43d1547d" id="u43d1547d"><span data-lake-id="u50a49922" id="u50a49922">​</span><br></p>
  <p data-lake-id="u3b95a521" id="u3b95a521"><span data-lake-id="u1b822f4e" id="u1b822f4e">这样做可以很大程度上的避免因为ThreadLocal的使用而导致的OOM问题，但是这个问题却无法彻底避免。</span></p>
  <p data-lake-id="u6352df05" id="u6352df05"><br></p>
  <h3 data-lake-id="Xlm0t" id="Xlm0t"><span data-lake-id="uf7db409d" id="uf7db409d">手动清理ThreadLocal</span></h3>
  <p data-lake-id="ub140f8e1" id="ub140f8e1"><br></p>
  <p data-lake-id="ue7e50619" id="ue7e50619"><span data-lake-id="u973ebe78" id="u973ebe78">上面的弱引用，解决了ThreadLocal中key的引用一直在无法被回收的问题，但是value的问题还是没解决。</span></p>
  <p data-lake-id="ue79cc4a9" id="ue79cc4a9"><br></p>
  <blockquote data-lake-id="u128ef674" id="u128ef674">
   <p data-lake-id="u296ddc08" id="u296ddc08"><span data-lake-id="u4472d79a" id="u4472d79a">Thread对象如果一直在被使用，比如在线程池中被重复使用，那么从这条引用链就一直在，那么就会导致ThreadLocalMap无法被回收。</span></p>
  </blockquote>
  <p data-lake-id="u403a1246" id="u403a1246"><span data-lake-id="uc085c1a9" id="uc085c1a9">​</span><br></p>
  <p data-lake-id="ubaaef846" id="ubaaef846"><img src="https://cdn.nlark.com/yuque/0/2023/png/5378072/1688455911220-b1033da7-8a0b-4f08-a083-f77f0bd2fc79.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_42%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"></p>
  <p data-lake-id="uf6a74fd8" id="uf6a74fd8"><br></p>
  <p data-lake-id="u0d06de28" id="u0d06de28"><span data-lake-id="uedfa9bef" id="uedfa9bef">因为我们可以看到，虽然key是弱引用，但是value的那条引用，还是个强引用呢！而且他的生命周期是和Thread一样的，也就是说，只要这个Thread还在， 这个对象就无法被回收。</span></p>
  <p data-lake-id="u50e7d91e" id="u50e7d91e"><span data-lake-id="uf0d0bc86" id="uf0d0bc86">​</span><br></p>
  <p data-lake-id="u1ee5b4f5" id="u1ee5b4f5"><span data-lake-id="u53d3bb73" id="u53d3bb73">那么，什么情况下，Thread会一直在呢？那就是线程池。</span></p>
  <p data-lake-id="ue3d93ce9" id="ue3d93ce9"><span data-lake-id="ue2b3ab7a" id="ue2b3ab7a">​</span><br></p>
  <p data-lake-id="u94c63aa1" id="u94c63aa1"><span data-lake-id="uf0012da2" id="uf0012da2">在线程池中，重复利用线程的时候，就会导致这个引用一直在，而value就一直无法被回收。</span></p>
  <p data-lake-id="u0ab2793e" id="u0ab2793e"><br></p>
  <p data-lake-id="u7a57acc3" id="u7a57acc3"><span data-lake-id="u463aabea" id="u463aabea">那么如何解决呢？</span></p>
  <p data-lake-id="u7ffa811f" id="u7ffa811f"><span data-lake-id="u9615f8ae" id="u9615f8ae">​</span><br></p>
  <p data-lake-id="ue799491c" id="ue799491c"><span data-lake-id="udb351f73" id="udb351f73">ThreadLocalMap底层使用数组来保存元素，使用“线性探测法”来解决hash冲突的，在每次调用ThreadLocal的get、set、remove等方法的时候，内部会实际调用ThreadLocalMap的get、set、remove等操作。</span></p>
  <p data-lake-id="u90dfdcb8" id="u90dfdcb8"><span data-lake-id="ua7666e43" id="ua7666e43">​</span><br></p>
  <p data-lake-id="uc7d55dfc" id="uc7d55dfc"><span data-lake-id="uf51b07d0" id="uf51b07d0">而ThreadLocalMap的每次get、set、remove，都会清理key为null,但是value还存在的Entry。</span></p>
  <p data-lake-id="ub865ba3a" id="ub865ba3a"><span data-lake-id="u4392d1de" id="u4392d1de">​</span><br></p>
  <p data-lake-id="u66bf7103" id="u66bf7103"><strong><span data-lake-id="ufc26e5e0" id="ufc26e5e0">所以，当我们在一个ThreadLocal用完之后，手动调用一下remove，就可以在下一次GC的时候，把Entry清理掉。</span></strong></p>
 </body>
</html>